// Copyright 2009 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: Gunnar Ritter

#include "probes/imapprobe.h"
#include "probes/linereader.h"
#include "probes/imapprobe_test.h"

namespace popdiag {

static const QString kUsername_ = "user@imapprobe.test.example";
static const QString kPassword_ = "password";

static enum State state_;
static enum Test test_;
static enum Actions actions_;

static QString tag_;
static int message_number_;
static int messages_fetched_;

void TestImapProbe::StartImapProbe() {
  state_ = STATE_BANNER;
  actions_ = ACTIONS_GOOD;
  probe_running_ = true;
  messages_fetched_ = 0;
  ImapProbe probe(kUsername_, kPassword_, "imap.test.example",
                  QHostAddress::Null, test_ == TEST_PULL_ALL_MAIL, true);
  CHECK(connect(&probe, SIGNAL(ProbeComplete(ResultStatus)),
                this, SLOT(OnProbeComplete(ResultStatus))));
  probe.StartProbe();
  // Without this, the calls to QTimer::singleShot() in
  // RunWhenEventQueueEmpties() which the IMAP reader
  // makes are apparently not always effective.
  while (probe_running_)
    qApp->processEvents();
  QVERIFY(actions_ == ACTIONS_GOOD);
}

void TestImapProbe::OnProbeComplete(ResultStatus result) {
  result_ = result;
  probe_running_ = false;
}

void TestImapProbe::TestStandardRun() {
  test_ = TEST_STANDARD_RUN;
  StartImapProbe();
  QVERIFY(state_ == STATE_PASSED);
  QVERIFY(result_ == PROBE_OK);
  QVERIFY(messages_fetched_ == 1);
}

void TestImapProbe::TestPullAllMail() {
  test_ = TEST_PULL_ALL_MAIL;
  StartImapProbe();
  QVERIFY(state_ == STATE_PASSED);
  QVERIFY(result_ == PROBE_OK);
  QVERIFY(messages_fetched_ == 2);
}

void TestImapProbe::TestPreauth() {
  test_ = TEST_PREAUTH;
  StartImapProbe();
  QVERIFY(state_ == STATE_PASSED);
  QVERIFY(result_ == PROBE_OK);
  QVERIFY(messages_fetched_ == 1);
}

void TestImapProbe::TestExpunge() {
  test_ = TEST_EXPUNGE;
  StartImapProbe();
  QVERIFY(state_ == STATE_PASSED);
  QVERIFY(result_ == PROBE_OK);
  QVERIFY(messages_fetched_ == 1);
}

void TestImapProbe::TestBannerFailure() {
  test_ = TEST_BANNER_FAILURE;
  StartImapProbe();
  QVERIFY(state_ == STATE_FAILED);
  QVERIFY(result_ == PROBE_FAILED);
  QVERIFY(messages_fetched_ == 0);
}

void TestImapProbe::TestCapabilityFailure() {
  test_ = TEST_CAPABILITY_FAILURE;
  StartImapProbe();
  QVERIFY(state_ == STATE_FAILED);
  QVERIFY(result_ == PROBE_FAILED);
  QVERIFY(messages_fetched_ == 0);
}

void TestImapProbe::TestLoginFailure() {
  test_ = TEST_LOGIN_FAILURE;
  StartImapProbe();
  QVERIFY(state_ == STATE_FAILED);
  QVERIFY(result_ == PROBE_FAILED);
  QVERIFY(messages_fetched_ == 0);
}

void TestImapProbe::TestSelectFailure() {
  test_ = TEST_SELECT_FAILURE;
  StartImapProbe();
  QVERIFY(state_ == STATE_FAILED);
  QVERIFY(result_ == PROBE_FAILED);
  QVERIFY(messages_fetched_ == 0);
}

void TestImapProbe::TestFetchFlagsFailure() {
  test_ = TEST_FETCH_FLAGS_FAILURE;
  StartImapProbe();
  QVERIFY(state_ == STATE_FAILED);
  QVERIFY(result_ == PROBE_FAILED);
  QVERIFY(messages_fetched_ == 0);
}

void TestImapProbe::TestFetchDataFailure() {
  test_ = TEST_FETCH_DATA_FAILURE;
  StartImapProbe();
  QVERIFY(state_ == STATE_FAILED);
  QVERIFY(result_ == PROBE_FAILED);
  QVERIFY(messages_fetched_ == 0);
}

void TestImapProbe::TestLogoutFailure() {
  test_ = TEST_LOGOUT_FAILURE;
  StartImapProbe();
  QVERIFY(state_ == STATE_FAILED);
  QVERIFY(result_ == PROBE_FAILED);
  QVERIFY(messages_fetched_ == 1);
}

void TestImapProbe::TestMissingExists() {
  test_ = TEST_MISSING_EXISTS;
  StartImapProbe();
  QVERIFY(state_ == STATE_WAITING);
  QVERIFY(result_ == PROBE_FAILED);
  QVERIFY(messages_fetched_ == 0);
}

LineReader::LineReader(RenamableSslSocket *src, int timeoutInMillis)
    : src_(src), timeout_(timeoutInMillis) {
  CHECK(connect(src_, SIGNAL(readyRead()),
                 this, SLOT(OnDataReady())));
}

LineReader::~LineReader() {
}

bool LineReader::NonblockingRead(QString *out) {
  switch (state_) {
    case STATE_PASSED:
      QWARN("attempt to read after the test already passed");
      actions_ = ACTIONS_BAD;
      return false;
    case STATE_FAILED:
      QWARN("attempt to read after the test already failed");
      actions_ = ACTIONS_BAD;
      return false;
    case STATE_WAITING:
      QWARN("attempt to read but no data present");
      actions_ = ACTIONS_BAD;
      return false;
    case STATE_BANNER:
      switch (test_) {
        case TEST_BANNER_FAILURE:
          *out = "* NO not serving";
          state_ = STATE_FAILED;
          break;
        case TEST_PREAUTH:
          *out = "* PREAUTH Gimap ready for requests from 127.0.0.1";
          state_ = STATE_WAITING;
          break;
        default:
          *out = "* OK Gimap ready for requests from 127.0.0.1";
          state_ = STATE_WAITING;
      }
      break;
    case STATE_CAPABILITY_LISTING:
      if (test_ == TEST_CAPABILITY_FAILURE) {
        *out = tag_ + " NO cannot do this";
        state_ = STATE_FAILED;
      } else {
        *out = "* CAPABILITY IMAP4rev1";
        state_ = STATE_CAPABILITY_RESPONSE;
      }
      break;
    case STATE_CAPABILITY_RESPONSE:
      *out = tag_ + " OK";
      state_ = STATE_WAITING;
      break;
    case STATE_LOGIN:
      if (test_ == TEST_LOGIN_FAILURE) {
        *out = tag_ + " NO won't let you in";
        state_ = STATE_FAILED;
      } else {
        *out = tag_ + " OK";
        state_ = STATE_WAITING;
      }
      break;
    case STATE_SELECT_FLAGS:
      *out = "* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)";
      if (test_ == TEST_MISSING_EXISTS)
        state_ = STATE_SELECT_RECENT;
      else
        state_ = STATE_SELECT_EXISTS;
      break;
    case STATE_SELECT_EXISTS:
      *out = "* 2 EXISTS";
      state_ = STATE_SELECT_RECENT;
      break;
    case STATE_SELECT_RECENT:
      *out = "* 0 RECENT";
      state_ = STATE_SELECT_UNSEEN;
      break;
    case STATE_SELECT_UNSEEN:
      *out = "* OK [UNSEEN 0]";
      state_ = STATE_SELECT_PERMANTENTFLAGS;
      break;
    case STATE_SELECT_PERMANTENTFLAGS:
      *out = "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Draft \\Deleted "
             "\\Seen)]";
      state_ = STATE_SELECT_UIDNEXT;
      break;
    case STATE_SELECT_UIDNEXT:
      *out = "* OK [UIDNEXT 3]";
      state_ = STATE_SELECT_UIDVALIDITY;
      break;
    case STATE_SELECT_UIDVALIDITY:
      *out = "* OK [UIDVALIDITY 611052928]";
      state_ = STATE_SELECT_RESPONSE;
      break;
    case STATE_SELECT_RESPONSE:
      if (test_ == TEST_SELECT_FAILURE) {
        *out = tag_ + " NO something is wrong with INBOX";
        state_ = STATE_FAILED;
      } else {
        *out = tag_ + " OK [READ-WRITE] INBOX selected.";
        state_ = STATE_WAITING;
      }
      break;
    case STATE_FETCH_FLAGS1:
      *out = "* 1 FETCH (UID 1 RFC822.SIZE 22)";
      state_ = STATE_FETCH_FLAGS2;
      break;
    case STATE_FETCH_FLAGS2:
      *out = "* 2 FETCH (UID 2 RFC822.SIZE 22)";
      if (test_ == TEST_EXPUNGE)
        state_ = STATE_EXPUNGE;
      else
        state_ = STATE_FETCH_FLAGS_RESPONSE;
      break;
    case STATE_EXPUNGE:
      *out = "* 1 EXPUNGE";
      state_ = STATE_FETCH_FLAGS_RESPONSE;
      break;
    case STATE_FETCH_FLAGS_RESPONSE:
      if (test_ == TEST_FETCH_FLAGS_FAILURE) {
        *out = tag_ + " NO whatever";
        state_ = STATE_FAILED;
      } else {
        *out = tag_ + " OK";
        state_ = STATE_WAITING;
      }
      break;
    case STATE_FETCH_DATA1:
      if (test_ == TEST_FETCH_DATA_FAILURE) {
        *out = tag_ + " NO disk error";
        state_ = STATE_FAILED;
      } else {
        *out = "* " + QString::number(message_number_) + " FETCH (BODY[] {22}";
        state_ = STATE_FETCH_DATA_CONTENTS;
      }
      break;
    case STATE_FETCH_DATA_CONTENTS:
      *out = "12345678901234567890";
      state_ = STATE_FETCH_DATA_RPAREN;
      break;
    case STATE_FETCH_DATA_RPAREN:
      *out = ")";
      state_ = STATE_FETCH_DATA_RESPONSE;
      break;
    case STATE_FETCH_DATA_RESPONSE:
      *out = tag_ + " OK";
      state_ = STATE_WAITING;
      ++messages_fetched_;
      break;
    case STATE_LOGOUT1:
      if (test_ == TEST_LOGOUT_FAILURE) {
        *out = tag_ + " NO would miss you so much";
        state_ = STATE_FAILED;
      } else {
        *out = "* BYE LOGOUT Requested";
        state_ = STATE_LOGOUT2;
      }
      break;
    case STATE_LOGOUT2:
      *out = tag_ + " OK";
      state_ = STATE_PASSED;
      break;
  }
  out->append("\r\n");
  return true;
}

void LineReader::TryRead(bool send_signals) {
  if (send_signals && state_ != STATE_WAITING)
    emit DataReady(DATA_READ);
}

void LineReader::Cleanup() {
}

void LineReader::OnTimeout() {
}

void LineReader::OnDataReady() {
  emit DataReady(DATA_READ);
}

void RenamableSslSocket::connectToHost(const QHostAddress &/*address*/,
                                       quint16 /*port*/) {
  emit connected();
}

qint64 RenamableSslSocket::write(const QByteArray &byteArray) {
  if (state_ == STATE_FAILED) {
    QWARN("attempt to write after the test already failed");
    actions_ = ACTIONS_BAD;
    return -1;
  }
  if (state_ == STATE_PASSED) {
    QWARN("attempt to write after the test already passed");
    actions_ = ACTIONS_BAD;
    state_ = STATE_FAILED;
    return -1;
  }
  QByteArray data = byteArray;
  while (data.endsWith('\n') or data.endsWith('\r'))
    data.chop(1);
  QList<QByteArray> words = data.split(' ');
  if (words.size() < 2) {
    QWARN("invalid request " + data + "\n");
    actions_ = ACTIONS_BAD;
    state_ = STATE_FAILED;
    return -1;
  }
  tag_ = words.at(0);
  QByteArray cmd = words.at(1);
  if (cmd == "CAPABILITY")
    state_ = STATE_CAPABILITY_LISTING;
  else if (cmd == "LOGIN") {
    if (test_ == TEST_PREAUTH) {
      QWARN("LOGIN command sent though PREAUTH was given");
      state_ = STATE_FAILED;
    } else if (words.at(2) != "\"" + kUsername_ + "\"") {
      QWARN("wrong username passed to LOGIN");
      state_ = STATE_FAILED;
    } else if (words.at(3) != "\"" + kPassword_ + "\"") {
      QWARN("wrong password passed to LOGIN");
      state_ = STATE_FAILED;
    } else {
      state_ = STATE_LOGIN;
    }
  } else if (cmd == "SELECT") {
    state_ = STATE_SELECT_FLAGS;
  } else if (cmd == "FETCH" && words.at(2) == "1:2" &&
             words.at(3) == "(RFC822.SIZE" && words.at(4) == "UID)") {
    state_ = STATE_FETCH_FLAGS1;
  } else if (cmd == "FETCH" && words.at(3) == "(BODY.PEEK[])") {
    state_ = STATE_FETCH_DATA1;
    message_number_ = words.at(2).toInt();
    if (message_number_ < 1 || message_number_ > 2) {
      QWARN("invalid message number " + QByteArray::number(message_number_));
      state_ = STATE_FAILED;
    }
  } else if (cmd == "LOGOUT") {
    state_ = STATE_LOGOUT1;
  } else {
    QWARN("unknown response " + data);
    actions_ = ACTIONS_BAD;
    state_ = STATE_FAILED;
  }
  emit readyRead();
  return byteArray.size();
}

void RenamableSslSocket::startClientEncryption() {
  emit encrypted();
  emit readyRead();
}

bool RenamableSslSocket::flush() {
  return false;
}

}  // namespace popdiag

QTEST_MAIN(popdiag::TestImapProbe)
